package com.pan.note.message;

import com.alibaba.fastjson.JSON;
import com.pan.note.enumerate.RedisDataEnum;
import com.pan.note.dao.ChatInfoDao;
import com.pan.note.mapper.ChatInfoMapper;
import com.pan.note.vo.WebSocketMsgVo;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * 自定义处理类
 */
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private ChatInfoMapper chatMapper;

    /**
     * 定义通道列表
     */
    public static ConcurrentHashMap<String, Channel> channelMap = new ConcurrentHashMap<>();


    /**
     * 通道就绪事件
     *
     * @param ctx 上下文
     * @throws Exception 异常
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        log.info("有新的连接：{}",channel.id().asLongText());
    }


    /**
     * 通道未就绪--channel下线
     * @param ctx 上下文
     * @throws Exception 异常
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        Channel channel = ctx.channel();
        //当有客户端断开连接的时候,就移除对应的通道
        //同时根据channel查询到对应的key（用户名），从redis中删除这条在线信息
        Object key = getKey(channelMap, channel);
        Map entries = redisTemplate.opsForHash().entries(RedisDataEnum.CHANNELBIND.getMsg());
        Object redisKey = getRedisKey(entries, key);
        log.info("channelMap的key:{},redis中的key:{}",key,redisKey);
        redisTemplate.opsForHash().delete(RedisDataEnum.CHANNELBIND.getMsg(),redisKey);
        log.info("删除redis中的通道");
        channelMap.remove(key);
        log.info("删除channelMap中的通道");
        channel.close();
        log.info("通道关闭");
    }

    /**
     * 读就绪事件
     *
     * @param ctx 上下文
     * @param textWebSocketFrame 传输文本
     * @throws Exception 异常
     * 读取出websocket数据后进行解析
     * 1.如果msgtype为0，则是身份认证数据，应该进行token解析，得到用户id和当前channel进行绑定
     * 2.如果msgtype为1，则为正常的聊天信息，则进行一对一或一对多转发
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame) throws Exception {
        String msg = textWebSocketFrame.text();
        log.info("msg:{}",msg);
        Channel channel = ctx.channel();
        WebSocketMsgVo webSocketMsgVo = JSON.parseObject(msg, WebSocketMsgVo.class);
        switch (webSocketMsgVo.getMsgType()){
            case 0:
                // 绑定用户信息
                bindUserChannel(webSocketMsgVo,channel);
                break;
            case 1:
                // 转发用户聊天
                transmitMsg(webSocketMsgVo);
                break;
            case 5:
                // 处理心跳
                sendHeartbeat(webSocketMsgVo,channel);
                 break;
            default:
                break;
        }
    }

    private void bindUserChannel(WebSocketMsgVo webSocketMsgVo,Channel channel){
        channelMap.put(webSocketMsgVo.getAccountId(),channel);
        log.info("用户和通道绑定");
        // 将绑定结果存入redis中
        redisTemplate.opsForHash().put(RedisDataEnum.CHANNELBIND.getMsg(), webSocketMsgVo.getAccountId(),channel.id().asLongText());
        log.info("用户id和通道唯一id绑定");
    }

    private void transmitMsg(WebSocketMsgVo webSocketMsgVo){
        // 查看对方在不在线，要是不在线就退出，在线就转发
        // 保存聊天记录
        chatMapper.insert(saveChatInfo(webSocketMsgVo));
        log.info("消息入库成功，状态未读");
        // 如果对方在线，就发送
        if (channelMap.containsKey(webSocketMsgVo.getSendToId())){
            Channel SendToChannel = channelMap.get(webSocketMsgVo.getSendToId());
            String jsonString = JSON.toJSONString(webSocketMsgVo);
            SendToChannel.writeAndFlush(new TextWebSocketFrame(jsonString));
            log.info("消息转发成功");
        }
    }

    private void sendHeartbeat(WebSocketMsgVo webSocketMsgVo,Channel channel){
        if(webSocketMsgVo.getMsgInfo().equals("ping")){
            WebSocketMsgVo pang = new WebSocketMsgVo();
            pang.setMsgType(5);
            pang.setMsgInfo("pang");
            String jsonString = JSON.toJSONString(pang);
            channel.writeAndFlush(new TextWebSocketFrame(jsonString));
            log.info("发送心跳");
        }
    }

    /**
     * 异常处理
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
    }

    /**
     * 根据value查询本地channel的key
     * @return key
     */
    public static Object getKey(ConcurrentHashMap map, Object value){
        for(Object key: map.keySet()){
            if(map.get(key).equals(value)){
                return key;
            }
        }
        return null;
    }

    /**
     * 根据value查询redis上的用户key
     * @return key
     */
    public static Object getRedisKey(Map map, Object value){
        for(Object key: map.keySet()){
            if(map.get(key).equals(value)){
                return key;
            }
        }
        return null;
    }

    public ChatInfoDao saveChatInfo(WebSocketMsgVo webSocketMsgDto){
        ChatInfoDao chatInfo = new ChatInfoDao();
        chatInfo.setSender(webSocketMsgDto.getAccountId());
        chatInfo.setReceiver(webSocketMsgDto.getSendToId());
        chatInfo.setChatInfo(webSocketMsgDto.getMsgInfo());
        chatInfo.setChatCheck(false);
        return chatInfo;
    }
}
